//++++++++++++++++++++++++++++++++++++++++++++
// ENBSeries effect file
// visit http://enbdev.com for updates
// Copyright (c) 2007-2017 Boris Vorontsov
//++++++++++++++++++++++++++++++++++++++++++++



//+++++++++++++++++++++++++++++
//internal parameters, can be modified
//+++++++++++++++++++++++++++++
//if uncommented, then enbwater.png used (change file name in code below)
//#define EXTERNALWATERTEXTURE

//caustics are very slow because procedural noise, replace with other code or animated texture
bool	ECausticsEnable
<
	string UIName="Water: EnableCaustics";
> = {true};

float	EReflectionIntensity
<
	string UIName="Water: ReflectionIntensity";
	string UIWidget="Spinner";
	float UIMin=0.0;
	float UIMax=1.0;
> = {0.3};

float	ECausticsIntensity
<
	string UIName="Water: CausticsIntensity";
	string UIWidget="Spinner";
	float UIMin=0.0;
	float UIMax=10.0;
> = {4.0};

float	ERefractionLength
<
	string UIName="Water: RefractionLength";
	string UIWidget="Spinner";
	float UIMin=0.0;
	float UIMax=1.0;
> = {0.5};

float	EWaveBumpiness
<
	string UIName="Water: WaveBumpiness";
	string UIWidget="Spinner";
	float UIMin=0.1;
	float UIMax=2.0;
> = {1.0};

texture2D texExternal
<
	string ResourceName="enbwater.png";
>;

sampler2D SamplerExternal = sampler_state
{
	Texture   = <texExternal>;
	MinFilter = LINEAR;
	MagFilter = LINEAR;
	MipFilter = LINEAR;
	AddressU  = Wrap;
	AddressV  = Wrap;
	SRGBTexture=FALSE;
	MaxMipLevel=0;
	MipMapLodBias=0;
};



//+++++++++++++++++++++++++++++
//external parameters, do not modify
//+++++++++++++++++++++++++++++
//keyboard controlled temporary variables (in some versions exists in the config file). Press and hold key 1,2,3...8 together with PageUp or PageDown to modify. By default all set to 1.0
float4	tempF1; //0,1,2,3
float4	tempF2; //5,6,7,8
float4	tempF3; //9,0
//x=Width, y=1/Width, z=ScreenScaleY, w=1/ScreenScaleY
float4	ScreenSize;
//changes in range 0..1, 0 means that night time, 1 - day time
float	ENightDayFactor;
//changes 0 or 1. 0 means that exterior, 1 - interior
float	EInteriorFactor;
//x=generic timer in range 0..1, period of 16777216 ms (4.6 hours), w=frame time elapsed (in seconds)
float4	Timer;
//additional info for computations
//float4	TempParameters; 
//fov in degrees
float	FieldOfView;
//time in 0..24 format
float	GameTime;
//constants set in enbhelper.dll, can be anything captured from game
float4	CustomShaderConstants1[8];


//transposed transform matrix view*projection and inverse of it
float4	MatrixVP[4];
float4	MatrixInverseVP[4];
float4	MatrixVPRotation[4];
float4	MatrixInverseVPRotation[4];
float4	MatrixView[4];
float4	MatrixInverseView[4];
float4	CameraPosition;


float4x4	MatrixWVP;
float4x4	MatrixWVPInverse;
float4x4	MatrixWorld;
float4x4	MatrixProj;
float4	FogParam; //x - nearclip, y - farclip, z - fog start, w - fog end
float4	FogFarColor;
float4	WaterParameters1; //x - depth scaling, y - mudiness factor, z - deepness factor, w - is camera underwater and how deep
float4	WaterParameters2; //timed animation of uv by function in ENBSeries


texture2D texOriginal; //scene color (LDR)
texture2D texRefl; //reflection/refraction
texture2D texEnv; //sky blurred
texture2D texDepth; //depth

sampler2D SamplerOriginal = sampler_state
{
    Texture   = <texOriginal>;
};

sampler2D SamplerRefl = sampler_state
{
	Texture   = <texRefl>;
	MinFilter = LINEAR;
	MagFilter = LINEAR;
	MipFilter = NONE;
	AddressU  = Clamp;
	AddressV  = Clamp;
	SRGBTexture=FALSE;
	MaxMipLevel=0;
	MipMapLodBias=0;
};

sampler2D SamplerEnv = sampler_state
{
	Texture   = <texEnv>;
	MinFilter = LINEAR;
	MagFilter = LINEAR;
	MipFilter = LINEAR;
	AddressU  = Clamp;
	AddressV  = Clamp;
	SRGBTexture=FALSE;
	MaxMipLevel=0;
	MipMapLodBias=0;
};

sampler2D SamplerDepth = sampler_state
{
	Texture   = <texDepth>;
	MinFilter = LINEAR;
	MagFilter = LINEAR;
	MipFilter = NONE;
	AddressU  = Clamp;
	AddressV  = Clamp;
	SRGBTexture=FALSE;
	MaxMipLevel=0;
	MipMapLodBias=0;
};

struct PS_OUTPUT3
{
	float4 Color[3] : COLOR0;
};

struct VS_INPUT
{
	float3	pos : POSITION;
	float4	diff : COLOR0;
	float4	spec : COLOR1;
	float2	txcoord0 : TEXCOORD0;
};

struct VS_OUTPUT
{
	float4	pos : POSITION;
	float4	diff : COLOR0;
	float2	txcoord0 : TEXCOORD0;
	float4	txcoord1 : TEXCOORD1;
	float3	normal : TEXCOORD2;
	float3	vnormal : TEXCOORD3;
	float4	vposition : TEXCOORD4;
	float3	wposition : TEXCOORD5;
	float3	eyedir : TEXCOORD6;
};



//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
float3	hash1(float3 p3)
{
	p3 = frac(p3 * float3(0.1031, 0.11369, 0.13787));
	p3 += dot(p3, p3.yxz+19.19);
	p3=float3((p3.x+p3.y), (p3.x+p3.z), (p3.y+p3.z)) * p3.zyx;
	return frac(p3);
//	return frac(float3((p3.x+p3.y)*p3.z, (p3.x+p3.z)*p3.y, (p3.y+p3.z)*p3.x));
}


float	WorleyNoise(float3 n, float scale)
{
	n *= scale;

	float2	dis=1.0;
	for (float x=-1.0; x<1.01; x+=1.0)
	{
		for (float y=-1.0; y<1.01; y+=1.0)
		{
			for (float z=-1.0; z<1.01; z+=1.0)
			{
				float3	xyz=float3(x,y,z);
				float3	frcn=frac(n);
				float3	p=n-frcn + xyz; //p=floor(n) + xyz;

				//tiling
				float3	pp = p;
			//	if (pp.x < 0.0) pp.x = scale + pp.x;
			//	if (pp.y < 0.0) pp.y = scale + pp.y;
				if (pp.z < 0.0) pp.z = scale + pp.z;
			//	if (pp.x > 1.0) pp.x = pp.x - scale;
			//	if (pp.y > 1.0) pp.y = pp.y - scale;
				if (pp.z > 1.0) pp.z = pp.z - scale;

				float2	sqrdist;
				frcn=xyz - frcn;
				p = hash1(pp) + frcn;
				sqrdist.x=dot(p, p);
				//dis.x = min(dis.x, sqrdist.x);
				pp.z+=0.012;
				p = hash1(pp) + frcn;
				sqrdist.y=dot(p, p);
				//sqrdist.xy = min(sqrdist.xy, abs(sqrdist.x + sqrdist.y)*0.5);
				dis.xy = min(dis.xy, sqrdist.xy);
			}
		}
	}
	dis.x=dis.x*dis.y;
	//return saturate(1.0 - sqrt(dis.x));
	return saturate(sqrt(dis.x));
}



VS_OUTPUT VS_Draw(VS_INPUT IN)
{
	VS_OUTPUT	OUT;

	float4	pos=float4(IN.pos.x,IN.pos.y,IN.pos.z,1.0);

	float4	tpos=mul(pos, MatrixWVP);

	//make water normal
	float3	cnormal=float3(0.0,0.0,1.0);
	//OUT.normal=cnormal.xyz;
	OUT.normal=mul(cnormal, MatrixWorld);
	OUT.vnormal=normalize(mul(cnormal.xyz, MatrixWVP));

	OUT.pos=tpos;
	OUT.vposition=tpos;
	OUT.wposition=mul(pos, MatrixWorld);

	float4	uv;
	uv.xy=IN.txcoord0.xy;
	uv.zw=0.0;

	OUT.txcoord0=IN.txcoord0;

	//v2
	uv.xy=IN.txcoord0.xy+WaterParameters2.w;
	uv.zw=IN.txcoord0.xy-WaterParameters2.w;
	uv.xy=0.02*pos.xy+WaterParameters2.w;
	//v1
//	uv.xy=0.004*pos.xy+WaterParameters2.xy;//make new uv macro set
//	uv.zw=0.003*pos.xy+WaterParameters2.zw;
	OUT.txcoord1=uv;

	OUT.diff=IN.diff; //diffuse color is bugged in game for big plane around player

	float3	campos=CameraPosition;
	//campos.x=MatrixInverseView[0].w;
	//campos.y=MatrixInverseView[1].w;
	//campos.z=MatrixInverseView[2].w;
	OUT.eyedir=(mul(pos, MatrixWorld) - campos);

 	return OUT;
}



//be careful with fixes in this code, because water also additinal waves from boats and they starts to look buggy if not handled properly by shader
PS_OUTPUT3 PS_Draw(VS_OUTPUT IN, float2 vPos : VPOS, uniform sampler2D ColorTexture)
{
	float4	res;
	float4	color;
	float2	coord;
	float2	uv1;
	float2	uv2;
	float3	weyedir;
	float3	untransnormal;
	float4	origcolor;
	float2	txcoord1;
	float3	normal;
	float4	tvec;

	float2	offset=0.0;

	txcoord1.xy=(IN.vposition.xy /IN.vposition.w)*float2(0.5, -0.5) + 0.5;
	txcoord1.xy+=0.5*float2(ScreenSize.y, ScreenSize.y*ScreenSize.z);

	weyedir=normalize(-IN.eyedir.xyz);

	uv1.xy=IN.txcoord0;
	uv2.xy=IN.txcoord1;

	color=tex2D(ColorTexture, uv1);//original
	color+=tex2D(ColorTexture, uv2);//second animated
	origcolor=color*0.5;

	//simplest parallax
	//float2	uvshift=IN.eyedir.xy * (1.0-origcolor.x) * 0.002;
	float2	uvshift=-IN.eyedir.xy * (origcolor.x*2.0-1.0) * 0.002;
	uv1.xy+=uvshift;
	uv2.xy+=uvshift;
	color=tex2D(ColorTexture, uv1);//original
	color+=tex2D(ColorTexture, uv2);//second animated
	origcolor=color*0.5;

	//generate normal map from texture
	float2	kernel[4]=
	{
		float2( 1.0, 0.0),
		float2(-1.0, 0.0),
		float2( 0.0, 1.0),
		float2( 0.0,-1.0)
	};
	for (int i=0; i<4; i++)
	{
		coord=kernel[i];
		float4	tempcolor;
		float4	tempcolor2;
		tempcolor=tex2D(ColorTexture, coord*0.0025 + uv1.xy);
		tempcolor2=tex2D(ColorTexture, coord*0.0025 + uv2.xy);
		tempcolor.x+=tempcolor2.x;
		offset+=coord*(color.x-tempcolor.x);
	}
	untransnormal.xy=offset.xy * 10.0*EWaveBumpiness; //bumpiness
	untransnormal.z=sqrt(1.0-dot(offset.xy, offset.xy));
	//untransnormal=normalize(untransnormal);
	untransnormal=normalize(mul(untransnormal, MatrixWorld));

	tvec.xyz=untransnormal;
	normal.x=dot(tvec.xyz, MatrixView[0]);
	normal.y=dot(tvec.xyz, MatrixView[1]);
	normal.z=dot(tvec.xyz, MatrixView[2]);
	normal=normalize(normal);


	offset.xy=normal.xy;
	offset.y=-offset.y;
	offset.xy*=0.05*ERefractionLength; //refraction length

	float3	refl=reflect(weyedir, untransnormal);

	float	objdepth=IN.vposition.z/IN.vposition.w;
	float	nonlinearobjdepth=objdepth;
	objdepth=1.0/max(1.0-objdepth, 0.000000001);

	float	planardepth=tex2D(SamplerDepth, txcoord1.xy).r;

	float	depth=tex2D(SamplerDepth, txcoord1.xy+offset.xy).r;

	if (nonlinearobjdepth>depth)
	{
		depth=planardepth;
		offset.xy=0.0;
	}

	float	scenedepth=depth;

	//to simplify calculations do not use real positions as linear depth
	planardepth=1.0/max(1.0-planardepth,0.000000001);

	depth=1.0/max(1.0-depth,0.000000001);

	float	depthfact=(depth-objdepth)*WaterParameters1.z;//WaterDeepness
	depthfact=depthfact*WaterParameters1.x;//correct depth by far/near clip planes

	depthfact=depthfact/(depthfact*0.95+1.0);

	if (scenedepth>0.99999) depthfact=1.0;

	//if underwater, disable fade
	float	backside=saturate(WaterParameters1.w*10.0);
	//depthfact*=saturate(1.0-WaterParameters1.w*10.0);
	depthfact*=1.0-backside;

	float	shorefade=(planardepth-objdepth);
	shorefade=shorefade*WaterParameters1.x;//correct depth by far/near clip planes
	shorefade=saturate(shorefade*1000.0 - 0.05);

	depthfact*=shorefade;
	offset.xy*=shorefade;

	//decrease normal on shore for reducing artifacts
	tvec.xyz=untransnormal;
	tvec.xy*=shorefade;
	normal.x=dot(tvec.xyz, MatrixView[0]);
	normal.y=dot(tvec.xyz, MatrixView[1]);
	normal.z=dot(tvec.xyz, MatrixView[2]);
	normal=normalize(normal);
	untransnormal=normalize(tvec.xyz);

	//refraction
	res=tex2D(SamplerRefl, txcoord1.xy+offset.xy);

	//+++ caustics begin
	float4 tempvec;
	float4 worldpos;
	float4 normalpos;

	//position of pixel
	//tempvec.xy=(txcoord1.xy+offset.xy)*2.0-1.0;//this is correct, but too big distortion, caustics don't look impressive
	//tempvec.xy=(txcoord1.xy)*2.0-1.0;
	tempvec.xy=(txcoord1.xy+offset.xy*0.2)*2.0-1.0;
	tempvec.y=-tempvec.y;
	tempvec.z=scenedepth;
	tempvec.w=1.0;
	//get world position
	worldpos.x=dot(tempvec, MatrixInverseVPRotation[0]);
	worldpos.y=dot(tempvec, MatrixInverseVPRotation[1]);
	worldpos.z=dot(tempvec, MatrixInverseVPRotation[2]);
	worldpos.w=dot(tempvec, MatrixInverseVPRotation[3]);
	worldpos.xyz/=worldpos.w;
	worldpos.xyz+=CameraPosition;


	float	deepfade=max(IN.wposition.z-worldpos.z, 0.0);
//	deepfade*=0.05; //deepness scaling
//	deepfade=saturate(deepfade);
	deepfade*=0.1; //deepness scaling
	deepfade=deepfade/(deepfade+1.0);

	tempvec.xyz=worldpos-CameraPosition;
	float	distfact=dot(tempvec.xyz,tempvec.xyz);
	distfact=saturate(1.0-distfact*0.000025);

	float	scale=8.0;

	float3	causticspos=worldpos;
	causticspos*=0.25;
	causticspos.z*=0.25;
	causticspos.z+=Timer.x*1677.7216 * 2.0; //animation by time
	causticspos.z=frac(causticspos.z);

	float	caustic=0.0;
	//skip caustics if not visible
	float	usecaustic=0.0;
	if (ECausticsEnable==true) usecaustic=1.0;
	usecaustic*=distfact;
	usecaustic*=0.9-deepfade;
	if (usecaustic>=0.1)
	{
		caustic=WorleyNoise(causticspos.xyz, scale);
	}

	caustic=caustic*caustic; //increase contrast

	//increase contrast
	//caustic=pow(caustic, (1.0+deepfade));
	caustic*=lerp(1.0, caustic, deepfade);

	caustic=caustic-0.1;

	//fade on edge
	caustic*=shorefade;

	//deepness fade of caustics
	caustic*=1.0-deepfade;

	//fade everything above water surface
	caustic*=saturate((WaterParameters1.w+CameraPosition.z - worldpos.z));

	//fade by distance
	caustic*=distfact;

	//intensity of caustics
	caustic*=ECausticsIntensity;

	res.xyz*=(1.0+caustic);

	//+++ caustics end

	//modulate by color
	res.xyz=lerp(res.xyz, res.xyz*IN.diff.xyz, saturate(depthfact));

	//muddiness by color
	res.xyz=lerp(res.xyz, IN.diff.xyz*0.2, saturate(depthfact*WaterParameters1.y));

	//reflection by environment map as sky
	float	reflamount=saturate(1.0-dot(untransnormal, weyedir));
	reflamount*=reflamount;
	reflamount*=reflamount;
	reflamount*=reflamount;
//	float	selfrefl=saturate(dot(untransnormal, weyedir)*2.0+1.0);//reduce at self reflection
//	reflamount*=lerp(0.1, 1.0, selfrefl*selfrefl);
	reflamount*=shorefade;//reduce on shore
	reflamount=reflamount*EReflectionIntensity;//reduce reflection amount
	float3	refluv=refl;
	refluv=saturate(refl*0.5+0.5);
	float4	envcolor=tex2D(SamplerEnv, refluv.xz);
	res.xyz+=envcolor * reflamount;

	//apply full reflection factor when underwater (screen space reflection code maybe to add here?)
	float	stopfact=saturate(-dot(untransnormal, weyedir));

	float	anglefact=1.0-saturate(dot(IN.normal.xyz, -weyedir) * 3.0);
	anglefact*=anglefact;
	anglefact*=anglefact;
	stopfact=saturate(stopfact-anglefact);

	stopfact=saturate((0.1-stopfact)*10.0); //smooth mix
	stopfact*=backside; //if underwater or not
	res.xyz=lerp(res.xyz, origcolor.xyz*IN.diff.xyz, stopfact);

	//trying to fix some objects which also "water", better don't touch this code
	//water of firetruck have IN.diff.w fading
	float	alphafix=saturate((origcolor.a-0.95)*50.0);
	res.w=lerp(IN.diff.w*origcolor.a, 1.0, alphafix);
	res.xyz=lerp(origcolor.xyz*IN.diff.xyz, res.xyz, alphafix);

	//apply fog (required for shader model 3)
	float	fadefact=(FogParam.w - IN.vposition.z) / (FogParam.w - FogParam.z);
	res.xyz=lerp(FogFarColor.xyz, res.xyz, saturate(fadefact));

	//write defer normal and depth for using in other effects. Alpha of normal is mask for something useful
	float3	ssnormal=normal;//normalize(normal);
	ssnormal.yz=-ssnormal.yz;
	float4	res1;
	float4	res2;
	res1.xyz=ssnormal*0.5+0.5;
	//depth
	float	nonlineardepth=(IN.vposition.z/IN.vposition.w);
	//253.0/255.0 are peds, 254.0/255.0 vehicles, 255.0/255.0 scene. You can define own mask value which will be available in alpha of normal map
	res1.w=0.01;//252.0/255.0; //mask //if this value is too low, data is cut because of alpha blending
//	res1.w=saturate(1.0-shorefade);//252.0/255.0; //mask //if this value is too low, data is cut because of alpha blending
	//if transparent, write it value to mask. But maybe better to skip writing by COLORWRITEENABLE1=0?
	float	opacity=res.w;
	if (opacity<0.999) res1.w=opacity;

//	res.w*=shorefade; //

	res2=nonlineardepth; //depth
	PS_OUTPUT3	OUT;
	OUT.Color[0]=res;
	OUT.Color[1]=res1;
	OUT.Color[2]=res2;
	return OUT;
}



//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
technique Draw
{
	pass p0
    {
	VertexShader = compile vs_3_0 VS_Draw();
#ifdef EXTERNALWATERTEXTURE
	PixelShader  = compile ps_3_0 PS_Draw(SamplerExternal);
#else
	PixelShader  = compile ps_3_0 PS_Draw(SamplerOriginal);
#endif

//AlphaBlendEnable=FALSE;
//COLORWRITEENABLE1=ALPHA|RED|GREEN|BLUE;
//COLORWRITEENABLE2=ALPHA|RED|GREEN|BLUE;
//	SrcBlend=ONE;
//	DestBlend=ZERO;
//	ZWriteEnable=TRUE;
	AlphaTestEnable=FALSE;
//	AlphaBlendEnable=FALSE;
//	SrcBlend=SRCALPHA; //water blending of game
//	DestBlend=INVSRCALPHA; //water blending of game
//	SeparateAlphaBlendEnable=TRUE;
//	SrcBlendAlpha=ONE;
//	DestBlendAlpha=ONE;
	}
}


//use this in case of camera is underwater (right now it's same shaders)
technique DrawUnderwater
{
	pass p0
    {
	VertexShader = compile vs_3_0 VS_Draw();
#ifdef EXTERNALWATERTEXTURE
	PixelShader  = compile ps_3_0 PS_Draw(SamplerExternal);
#else
	PixelShader  = compile ps_3_0 PS_Draw(SamplerOriginal);
#endif
//	ZWriteEnable=TRUE;
	AlphaTestEnable=FALSE;
//	AlphaBlendEnable=FALSE;
//	SrcBlend=SRCALPHA;
//	DestBlend=INVSRCALPHA;
	}
}

